﻿// License: The MIT License (MIT) Copyright (c) 2011..2012 Barend Gehrels

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows;
using System.Xml.Linq;
using Stormy;

namespace GrooveSharp
{
    static class Importer
    {
        private class MapUtil
        {

            protected int TrackId(string fileName, string folderName)
            {
                return Db.SelectFirstOrThrow<int>
                    (
                        "select track_id from tracks where file_name = @file and folder_name = @folder",
                        new List<StormyParameter>() 
                                { 
                                    Db.CreateParameter(DbType.String, "@file", fileName),
                                    Db.CreateParameter(DbType.String, "@folder", folderName)
                                }
                    );
            }

            protected int FlagId(string flagName)
            {
                return Db.SelectFirstOrThrow<int>
                    (
                        "select track_flag_id from track_flags where track_flag_name = @flag",
                        new List<StormyParameter>() 
                                { 
                                    Db.CreateParameter(DbType.String, "@flag", flagName),
                                }
                    );
            }
        }

        private class PlayInfoMapper : MapUtil, IInsertable<ImpExp.PlayInfo>
        {
            public string InsertSql()
            {
                return "insert into plays(track_id,play_date,play_duration,user_id) values(@trackid,@date,@duration,@userid)";
            }

            public void ApplyInsert(ImpExp.PlayInfo item, IDbCommand command)
            {
                command.Parameters.Add(Db.CreateParameter(command, DbType.Int32, "@trackid", TrackId(item.FileName, item.FolderName)));
                command.Parameters.Add(Db.CreateParameter(command, DbType.Int32, "@userid", Db.UserId()));
                command.Parameters.Add(Db.CreateParameter(command, DbType.DateTime, "@date", item.PlayDate));

                if (item.Duration.HasValue)
                {
                    command.Parameters.Add(Db.CreateParameter(command, DbType.String, "@duration", item.Duration.ToString()));
                }
                else
                {
                    command.Parameters.Add(Db.CreateParameter(command, DbType.String, "@duration", null));
                }
            }
        }

        private class RatingInfoMapper : MapUtil, IInsertable<ImpExp.RatingInfo>
        {
            public string InsertSql()
            {
                return "insert into ratings(track_id,user_id,rating_value,rating_date) values(@trackid,@userid,@rating,@date)";
            }

            public void ApplyInsert(ImpExp.RatingInfo item, IDbCommand command)
            {
                command.Parameters.Add(Db.CreateParameter(command, DbType.Int32, "@trackid", TrackId(item.FileName, item.FolderName)));
                command.Parameters.Add(Db.CreateParameter(command, DbType.Int32, "@userid", Db.UserId()));
                command.Parameters.Add(Db.CreateParameter(command, DbType.Int32, "@rating", item.RatingValue));
                command.Parameters.Add(Db.CreateParameter(command, DbType.DateTime, "@date", item.PlayDate));
            }
        }

        private class FlagInfoMapper : MapUtil, IInsertable<ImpExp.FlagInfo>
        {
            public string InsertSql()
            {
                return "insert into link_track_flag(track_id,track_flag_id,user_id) values(@trackid,@flagid,@userid)";
            }

            public void ApplyInsert(ImpExp.FlagInfo item, IDbCommand command)
            {
                command.Parameters.Add(Db.CreateParameter(command, DbType.Int32, "@trackid", TrackId(item.FileName, item.FolderName)));
                command.Parameters.Add(Db.CreateParameter(command, DbType.Int32, "@flagid", FlagId(item.FlagName)));
                command.Parameters.Add(Db.CreateParameter(command, DbType.Int32, "@userid", Db.UserId()));
            }
        }

        private class PlayInfoEqualityComparer : IEqualityComparer<ImpExp.PlayInfo>
        {
            public bool Equals(ImpExp.PlayInfo item1, ImpExp.PlayInfo item2)
            {
                return item1.FileName == item2.FileName
                    && item1.FolderName == item2.FolderName
                    && item1.PlayDate == item2.PlayDate
                    ;
            }

            public int GetHashCode(ImpExp.PlayInfo info)
            {
                return info.FileName.GetHashCode() 
                    ^ info.FolderName.GetHashCode() 
                    ^ info.PlayDate.GetHashCode()
                    ;
            }
        }

        private class RatingEqualityComparer : IEqualityComparer<ImpExp.RatingInfo>
        {
            public bool Equals(ImpExp.RatingInfo item1, ImpExp.RatingInfo item2)
            {
                return item1.FileName == item2.FileName
                    && item1.FolderName == item2.FolderName
                    && item1.PlayDate == item2.PlayDate
                    && item1.RatingValue == item2.RatingValue
                    ;
            }

            public int GetHashCode(ImpExp.RatingInfo info)
            {
                return info.FileName.GetHashCode()
                    ^ info.FolderName.GetHashCode() 
                    ^ info.PlayDate.GetHashCode()
                    ^ info.RatingValue.GetHashCode()
                    ;
            }
        }

        private class FlagEqualityComparer : IEqualityComparer<ImpExp.FlagInfo>
        {
            public bool Equals(ImpExp.FlagInfo item1, ImpExp.FlagInfo item2)
            {
                return item1.FileName == item2.FileName
                    && item1.FolderName == item2.FolderName
                    && item1.FlagName == item2.FlagName
                    ;
            }

            public int GetHashCode(ImpExp.FlagInfo info)
            {
                return info.FileName.GetHashCode()
                    ^ info.FolderName.GetHashCode()
                    ^ info.FlagName.GetHashCode()
                    ;
            }
        }

        private static void Insert<Item>(IEnumerable<Item> collection, IInsertable<Item> mapper)
        {
            foreach (var info in collection)
            {
                try
                {
                    Db.Connection().Insert(info, mapper);
                }
                catch (ItemNotFoundException)
                {
                    // We leave this, we cannot insert play dates/tracks for a track (or flag) which is not in the database.
                }
            }
        }

        private static TimeSpan? TimeSpanFromString(string s)
        {
            if (s == "null")
            {
                return null;
            }
            return TimeSpan.Parse(s);
        }

        /*
         * The next three functions:
         * 1) retrieve a collection "itemsInXml" from the XML using Linq2Xml
         * 2) retrieve a similar collection (e.g. ImpExp.Plays()) from the database
         * 3) calculate the difference using Except and an EqualityComparer
         *    This difference are records IN the XML but not in the database
         * 4) insert this difference into the database using Insert<> and a mapper
         * 
         * All mappers and equality comparers are defined above.
         */

        private static void ImportPlays(XDocument doc)
        {
            var itemsInXml =
                    from element in doc.Descendants(ImpExp.XML_ELEMENT_PLAY)
                    select new ImpExp.PlayInfo()
                    {
                        FolderName = element.Attribute(ImpExp.XML_ATTRIBUTE_FOLDERNAME).Value,
                        FileName = element.Attribute(ImpExp.XML_ATTRIBUTE_FILENAME).Value,
                        PlayDate = DateTime.Parse(element.Attribute(ImpExp.XML_ATTRIBUTE_DATE).Value.ToString()),
                        Duration = TimeSpanFromString(element.Attribute(ImpExp.XML_ATTRIBUTE_DURATION).Value.ToString())
                    };

            Insert<ImpExp.PlayInfo>(itemsInXml.Except(ImpExp.Plays(), new PlayInfoEqualityComparer()), new PlayInfoMapper());
        }

        private static void ImportRatings(XDocument doc)
        {
            var itemsInXml =
                    from element in doc.Descendants(ImpExp.XML_ELEMENT_RATING)
                    select new ImpExp.RatingInfo()
                    {
                        FolderName = element.Attribute(ImpExp.XML_ATTRIBUTE_FOLDERNAME).Value,
                        FileName = element.Attribute(ImpExp.XML_ATTRIBUTE_FILENAME).Value,
                        PlayDate = DateTime.Parse(element.Attribute(ImpExp.XML_ATTRIBUTE_DATE).Value.ToString()),
                        RatingValue = Int32.Parse(element.Attribute(ImpExp.XML_ATTRIBUTE_RATING).Value.ToString())
                    };

            Insert<ImpExp.RatingInfo>(itemsInXml.Except(ImpExp.Ratings(), new RatingEqualityComparer()), new RatingInfoMapper());
        }

        private static void ImportFlags(XDocument doc)
        {
            var itemsInXml =
                    from element in doc.Descendants(ImpExp.XML_ELEMENT_FLAG)
                    select new ImpExp.FlagInfo()
                    {
                        FolderName = element.Attribute(ImpExp.XML_ATTRIBUTE_FOLDERNAME).Value,
                        FileName = element.Attribute(ImpExp.XML_ATTRIBUTE_FILENAME).Value,
                        FlagName = element.Attribute(ImpExp.XML_ATTRIBUTE_FLAG).Value
                    };

            Insert<ImpExp.FlagInfo>(itemsInXml.Except(ImpExp.Flags(), new FlagEqualityComparer()), new FlagInfoMapper());
        }

        public static void Import(XDocument doc)
        {
            ImportPlays(doc);
            ImportRatings(doc);
            ImportFlags(doc);
        }

    }

    static class AnalyzeAndImport
    {

        public static void Import(string fileName)
        {
            var doc = XDocument.Load(fileName);

            if (doc.Descendants(ImpExp.XML_ELEMENT_MAIN).Any())
            {
                // Exported by Groove#
                Importer.Import(doc);
            }
            else if (doc.Descendants(ImportITunes.XML_DICT).Any())
            {
                // Exported by iTunes
                ImportITunes.Import(doc);
            }
            else
            {
                MessageBox.Show(String.Format("Cannot import {0}. It is not recognized as an Groove# exported XML or iTunes exported XML", fileName), 
                        "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
    }
}
